1, T_MULTIPLY => 2, T_DIVIDE => 2, T_MODULUS => 3, T_PLUS => 4, T_MINUS => 4, T_STRING_CONCAT => 5, ]; public bool $ignoreComplexTernaryConditions = false; /** * @return array */ public function register(): array { return [ T_OPEN_PARENTHESIS, ]; } /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @param int $parenthesisOpenerPointer */ public function process(File $phpcsFile, $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); if (array_key_exists('parenthesis_owner', $tokens[$parenthesisOpenerPointer])) { return; } if (!array_key_exists('parenthesis_closer', $tokens[$parenthesisOpenerPointer])) { return; } /** @var int $pointerBeforeParenthesisOpener */ $pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1); if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], [ ...TokenHelper::NAME_TOKEN_CODES, T_VARIABLE, T_ISSET, T_UNSET, T_EMPTY, T_CLOSURE, T_FN, T_USE, T_ANON_CLASS, T_NEW, T_SELF, T_STATIC, T_PARENT, T_EXIT, T_CLOSE_PARENTHESIS, T_EVAL, T_LIST, T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, T_REQUIRE_ONCE, T_INT_CAST, T_DOUBLE_CAST, T_STRING_CAST, T_ARRAY_CAST, T_OBJECT_CAST, T_BOOL_CAST, T_UNSET_CAST, T_MATCH, T_BITWISE_NOT, ], true,)) { return; } /** @var int $pointerAfterParenthesisOpener */ $pointerAfterParenthesisOpener = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); if (in_array( $tokens[$pointerAfterParenthesisOpener]['code'], [T_CLONE, T_YIELD, T_YIELD_FROM, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE, T_ARRAY_CAST], true, )) { return; } if (TokenHelper::findNext( $phpcsFile, T_EQUAL, $parenthesisOpenerPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'], ) !== null) { return; } $pointerAfterParenthesisCloser = TokenHelper::findNextEffective( $phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1, ); if ( $pointerAfterParenthesisCloser !== null && $tokens[$pointerAfterParenthesisCloser]['code'] === T_OPEN_PARENTHESIS ) { return; } if (IdentificatorHelper::findStartPointer($phpcsFile, $pointerBeforeParenthesisOpener) !== null) { return; } $this->checkParenthesesAroundConditionInTernaryOperator($phpcsFile, $parenthesisOpenerPointer); $this->checkParenthesesAroundCaseInSwitch($phpcsFile, $parenthesisOpenerPointer); $this->checkParenthesesAroundVariableOrFunctionCall($phpcsFile, $parenthesisOpenerPointer); $this->checkParenthesesAroundString($phpcsFile, $parenthesisOpenerPointer); $this->checkParenthesesAroundOperators($phpcsFile, $parenthesisOpenerPointer); $this->checkParenthesesAroundNew($phpcsFile, $parenthesisOpenerPointer); } private function checkParenthesesAroundConditionInTernaryOperator(File $phpcsFile, int $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); $parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer']; $ternaryOperatorPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisCloserPointer + 1); if ($tokens[$ternaryOperatorPointer]['code'] !== T_INLINE_THEN) { return; } if (TokenHelper::findNext( $phpcsFile, [T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR], $parenthesisOpenerPointer + 1, $parenthesisCloserPointer, ) !== null) { return; } $pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1); if ($tokens[$pointerBeforeParenthesisOpener]['code'] === T_BOOLEAN_NOT) { return; } if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$comparisonTokens, true)) { return; } if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$booleanOperators, true)) { return; } if ($this->ignoreComplexTernaryConditions) { if (TokenHelper::findNext( $phpcsFile, Tokens::$booleanOperators, $parenthesisOpenerPointer + 1, $parenthesisCloserPointer, ) !== null) { return; } if (TokenHelper::findNextContent( $phpcsFile, T_WHITESPACE, $phpcsFile->eolChar, $parenthesisOpenerPointer + 1, $parenthesisCloserPointer, ) !== null) { return; } } $contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); $contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisCloserPointer - 1); for ($i = $contentStartPointer; $i <= $contentEndPointer; $i++) { if ($tokens[$i]['code'] === T_INLINE_THEN) { return; } } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); if (!$fix) { return; } $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1); FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $parenthesisCloserPointer); $phpcsFile->fixer->endChangeset(); } private function checkParenthesesAroundCaseInSwitch(File $phpcsFile, int $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); $pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1); if ($tokens[$pointerBeforeParenthesisOpener]['code'] !== T_CASE) { return; } $pointerAfterParenthesisCloser = TokenHelper::findNextEffective( $phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1, ); if ($tokens[$pointerAfterParenthesisCloser]['code'] !== T_COLON) { return; } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); if (!$fix) { return; } $contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); $contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1); $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1); FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']); $phpcsFile->fixer->endChangeset(); } private function checkParenthesesAroundVariableOrFunctionCall(File $phpcsFile, int $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); $pointerAfterParenthesis = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); if ($tokens[$pointerAfterParenthesis]['code'] === T_NEW) { // Check in other method return; } if ($tokens[$pointerAfterParenthesis]['code'] === T_OPEN_PARENTHESIS) { return; } $operatorsPointers = TokenHelper::findNextAll( $phpcsFile, self::OPERATORS, $parenthesisOpenerPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'], ); if ($operatorsPointers !== []) { return; } $casePointer = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1); if ($tokens[$casePointer]['code'] === T_CASE) { return; } $pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1); if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$booleanOperators, true)) { return; } $pointerAfterParenthesisCloser = TokenHelper::findNextEffective( $phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1, ); if (in_array($tokens[$pointerAfterParenthesisCloser]['code'], [T_INLINE_THEN, T_OPEN_PARENTHESIS, T_SR], true)) { return; } /** @var int $contentStartPointer */ $contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); if ($tokens[$contentStartPointer]['code'] === T_CONSTANT_ENCAPSED_STRING) { return; } $notBooleanNotOperatorPointer = $contentStartPointer; if ($tokens[$contentStartPointer]['code'] === T_BOOLEAN_NOT) { /** @var int $notBooleanNotOperatorPointer */ $notBooleanNotOperatorPointer = TokenHelper::findNextEffective($phpcsFile, $contentStartPointer + 1); } if (in_array( $tokens[$notBooleanNotOperatorPointer]['code'], [T_SELF, T_STATIC, T_PARENT, T_VARIABLE, T_DOLLAR, ...TokenHelper::NAME_TOKEN_CODES], true, )) { $contentEndPointer = IdentificatorHelper::findEndPointer($phpcsFile, $notBooleanNotOperatorPointer); if ( $contentEndPointer === null && in_array($tokens[$notBooleanNotOperatorPointer]['code'], TokenHelper::NAME_TOKEN_CODES, true) ) { $nextPointer = TokenHelper::findNextEffective($phpcsFile, $contentStartPointer + 1); if ($tokens[$nextPointer]['code'] === T_OPEN_PARENTHESIS) { $contentEndPointer = $contentStartPointer; } } do { $nextPointer = TokenHelper::findNextEffective($phpcsFile, $contentEndPointer + 1); if ($tokens[$nextPointer]['code'] !== T_OPEN_PARENTHESIS) { break; } $contentEndPointer = $tokens[$nextPointer]['parenthesis_closer']; } while (true); } else { $nextPointer = TokenHelper::findNext($phpcsFile, T_OPEN_PARENTHESIS, $notBooleanNotOperatorPointer + 1); if ($nextPointer === null || !isset($tokens[$nextPointer]['parenthesis_closer'])) { return; } $contentEndPointer = $tokens[$nextPointer]['parenthesis_closer']; } $pointerAfterContent = TokenHelper::findNextEffective($phpcsFile, $contentEndPointer + 1); if ($pointerAfterContent !== $tokens[$parenthesisOpenerPointer]['parenthesis_closer']) { return; } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); if (!$fix) { return; } $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1); FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']); $phpcsFile->fixer->endChangeset(); } private function checkParenthesesAroundString(File $phpcsFile, int $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); /** @var int $stringPointer */ $stringPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); if ($tokens[$stringPointer]['code'] !== T_CONSTANT_ENCAPSED_STRING) { return; } $pointerAfterString = TokenHelper::findNextEffective($phpcsFile, $stringPointer + 1); if ($pointerAfterString !== $tokens[$parenthesisOpenerPointer]['parenthesis_closer']) { return; } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); if (!$fix) { return; } $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $stringPointer - 1); FixerHelper::removeBetweenIncluding($phpcsFile, $stringPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']); $phpcsFile->fixer->endChangeset(); } private function checkParenthesesAroundOperators(File $phpcsFile, int $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); $newPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); if ($tokens[$newPointer]['code'] === T_NEW) { // Check in other method return; } $pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1); $pointerAfterParenthesisCloser = TokenHelper::findNextEffective( $phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1, ); if ($tokens[$pointerBeforeParenthesisOpener]['code'] === T_MINUS) { $pointerBeforeMinus = TokenHelper::findPreviousEffective($phpcsFile, $pointerBeforeParenthesisOpener - 1); if (!in_array($tokens[$pointerBeforeMinus]['code'], [T_DNUMBER, T_LNUMBER], true)) { return; } } if ( in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$booleanOperators, true) || in_array($tokens[$pointerAfterParenthesisCloser]['code'], Tokens::$booleanOperators, true) || $tokens[$pointerBeforeParenthesisOpener]['code'] === T_BOOLEAN_NOT ) { return; } $complicatedOperators = [T_INLINE_THEN, T_COALESCE, T_BITWISE_AND, T_BITWISE_OR, T_BITWISE_XOR, T_SL, T_SR]; $operatorsPointers = []; $actualStartPointer = $parenthesisOpenerPointer + 1; while (true) { $pointer = TokenHelper::findNext( $phpcsFile, array_merge( [ ...self::OPERATORS, T_OPEN_PARENTHESIS, ...$complicatedOperators, ], Tokens::$comparisonTokens, ), $actualStartPointer, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'], ); if ($pointer === null) { break; } if (in_array($tokens[$pointer]['code'], $complicatedOperators, true)) { return; } if (in_array($tokens[$pointer]['code'], Tokens::$comparisonTokens, true)) { return; } if ($tokens[$pointer]['code'] === T_OPEN_PARENTHESIS) { $actualStartPointer = $tokens[$pointer]['parenthesis_closer'] + 1; continue; } $operatorsPointers[] = $pointer; $actualStartPointer = $pointer + 1; } if (count($operatorsPointers) === 0) { return; } if ( $tokens[$pointerBeforeParenthesisOpener]['code'] !== T_EQUAL || $tokens[$pointerAfterParenthesisCloser]['code'] !== T_SEMICOLON ) { $operatorsGroups = array_map( static fn (int $operatorPointer): int => self::OPERATOR_GROUPS[$tokens[$operatorPointer]['code']], $operatorsPointers, ); if (count($operatorsGroups) > 1) { return; } } $firstOperatorPointer = $operatorsPointers[0]; if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], self::OPERATORS, true)) { if (self::OPERATOR_GROUPS[$tokens[$firstOperatorPointer]['code']] !== self::OPERATOR_GROUPS[$tokens[$pointerBeforeParenthesisOpener]['code']]) { return; } if ( $tokens[$pointerBeforeParenthesisOpener]['code'] === T_MINUS && in_array($tokens[$firstOperatorPointer]['code'], [T_PLUS, T_MINUS], true) ) { return; } if ( $tokens[$pointerBeforeParenthesisOpener]['code'] === T_DIVIDE && in_array($tokens[$firstOperatorPointer]['code'], [T_DIVIDE, T_MULTIPLY], true) ) { return; } if ( $tokens[$pointerBeforeParenthesisOpener]['code'] === T_MODULUS && $tokens[$firstOperatorPointer]['code'] === T_MODULUS ) { return; } } $lastOperatorPointer = $operatorsPointers[count($operatorsPointers) - 1]; if ( in_array($tokens[$pointerAfterParenthesisCloser]['code'], self::OPERATORS, true) && self::OPERATOR_GROUPS[$tokens[$lastOperatorPointer]['code']] !== self::OPERATOR_GROUPS[$tokens[$pointerAfterParenthesisCloser]['code']] ) { return; } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); if (!$fix) { return; } $contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); $contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1); $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1); FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']); $phpcsFile->fixer->endChangeset(); } private function checkParenthesesAroundNew(File $phpcsFile, int $parenthesisOpenerPointer): void { $tokens = $phpcsFile->getTokens(); $newPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); if ($tokens[$newPointer]['code'] !== T_NEW) { return; } $pointerAfterParenthesisCloser = TokenHelper::findNextEffective( $phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1, ); if (!in_array($tokens[$pointerAfterParenthesisCloser]['code'], [T_COMMA, T_SEMICOLON, T_CLOSE_SHORT_ARRAY], true)) { return; } $fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES); if (!$fix) { return; } $contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1); $contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1); $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1); FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']); $phpcsFile->fixer->endChangeset(); } }